package xxl.robotlegs.extensions.viewprocessormap.impl 
{
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.utils.Dictionary;
	import xxl.robotlegs.extensions.matching.api.ITypeFilter;
	import xxl.robotlegs.extensions.viewprocessormap.api.IViewProcessorConfigurator;
	import xxl.robotlegs.extensions.viewprocessormap.api.IViewProcessorFactory;
	import xxl.robotlegs.extensions.viewprocessormap.api.IViewProcessorMapping;
	import xxl.robotlegs.framework.api.IInjector;
	import xxl.robotlegs.utils.GuardUtil;
	import xxl.robotlegs.utils.HookUtil;
	import xxl.signals.api.ISlot;
	import xxl.signals.api.natives.INativeDispatcher;
	import xxl.signals.impl.natives.NativeSignal;
	import xxl.signals.impl.natives.sets.DisplayObjectSignalSet;
	/**
	 * ...
	 * @author XXL
	 */
	public class ViewProcessorFactory implements IViewProcessorFactory
	{
		private var _injector:IInjector;
		private const _listenersByView:Dictionary = new Dictionary(true);
		
		public function ViewProcessorFactory(injector:IInjector) 
		{
			_injector = injector;
		}
		
		private function createRemovedListener(view:Object, type:Class, processorMappings:Array):void
		{
			if (view is DisplayObject)
			{
				const dis:DisplayObject = view as DisplayObject;
				_listenersByView[view] ||= new DisplayObjectSignalSet(dis, true);
				const disSet:DisplayObjectSignalSet = _listenersByView[view];
				disSet.removedFromStage.addOnce(removeHandler).params = [view, type, processorMappings];
			}
		}
		
		private function removeHandler(e:Event, view:DisplayObject, type:Class, processorMappings:Array):void
		{
			runUnprocessors(view, type, processorMappings);
			removeHandlerFromView(view, removeHandler);
		}
		
		private function removeHandlerFromView(view:Object, handler:Function):void
		{
			const disSet:DisplayObjectSignalSet = _listenersByView[view];
			if (disSet && disSet.removedFromStage.numListeners <= 0)
			{
				delete _listenersByView[view];
			}
		}
		
		private function mapTypeForFilterBinding(filter:ITypeFilter, type:Class, view:Object):void
		{
			const requiredTypes:Vector.<Class> = requiredTypesFor(filter, type);
			const len:int = requiredTypes.length;
			for (var i:int = 0; i < len; i++) 
			{
				_injector.map(requiredTypes[i]).toValue(view);
			}
		}
		
		private function requiredTypesFor(filter:ITypeFilter, type:Class):Vector.<Class>
		{
			const requiredTypes:Vector.<Class> = filter.allOfTypes.concat(filter.anyOfTypes);
			if (requiredTypes.indexOf(type) < 0)
			{
				requiredTypes.push(type);
			}
			return requiredTypes;
		}
		
		private function unmapTypeForFilterBinding(filter:ITypeFilter, type:Class, view:Object):void
		{
			const requiredTypes:Vector.<Class> = requiredTypesFor(filter, type);
			const len:int = requiredTypes.length;
			for (var i:int = 0; i < len; i++) 
			{
				const requiredType:Class = requiredTypes[i];
				if (_injector.hasDirectMapping(requiredType))
				{
					_injector.unmap(requiredType);
				}
			}
		}
		
		private function createProcessor(processorClass:Class):Object
		{
			if (!_injector.hasMapping(processorClass))
			{
				_injector.map(processorClass).asSingleton();
			}
			return _injector.getInstance(processorClass);
		}
		
		private function runProcess(view:Object, type:Class, mapping:IViewProcessorMapping):void
		{
			if (GuardUtil.GuardsApprove(mapping.guards, _injector))
			{
				mapping.processor ||= createProcessor(mapping.processorClass);
				HookUtil.ApplyHooks(mapping.hooks, _injector);
				mapping.processor.process(view, type, _injector);
			}
		}
		
		/* INTERFACE xxl.robotlegs.extensions.viewprocessormap.api.IViewProcessorFactory */
		
		public function runProcessors(view:Object, type:Class, processorMappings:Array):void 
		{
			createRemovedListener(view, type, processorMappings);
			
			const len:int = processorMappings.length;
			for (var i:int = 0; i < len; i++) 
			{
				const mapping:IViewProcessorMapping = processorMappings[i];
				const filter:ITypeFilter = mapping.matcher;
				mapTypeForFilterBinding(filter, type, view);
				runProcess(view, type, mapping);
				unmapTypeForFilterBinding(filter, type, view);
			}
		}
		
		public function runUnprocessors(view:Object, type:Class, processorMappings:Array):void 
		{
			const len:int = processorMappings.length;
			for (var i:int = 0; i < len; i++) 
			{
				const mapping:IViewProcessorMapping = processorMappings[i];
				mapping.processor ||= createProcessor(mapping.processorClass);
				mapping.processor.unprocess(view, type, _injector);
			}
		}
		
		public function runAllUnprocessors():void 
		{
			for each(var disSet:DisplayObjectSignalSet in _listenersByView)
			{
				const dispatcher:INativeDispatcher = disSet.removedFromStage;
				dispatcher.dispatchEvent(null);
			}
		}
		
	}

}